package cuatroenraya.jugadores;

import cuatroenraya.Jugador;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * Clase Jugador que juega solo basándose en heurísticas.
 * @author
 * Daniel Acevedo (Leg: 49358)
 * Juan Brusasca (Leg: 38227)
 * Marcelo Busico (Leg: 49145)
 * David Canteros (Leg: 47616)
 */
public class JugadorJack extends Jugador {

    private int posicionJugador;
    private Tablero tablero;
    private Set<Raya> rayasPropias = new TreeSet<Raya>();
    private Set<Raya> rayasRival = new TreeSet<Raya>();
    private Set<Raya> rayasVacias = new TreeSet<Raya>();

    public JugadorJack(boolean isJugadorA) {
        nombre = "JugadorBBC";
        if (isJugadorA) {
            posicionJugador = 1;
        } else {
            posicionJugador = 2;
        }
    }

    /**
     * Implementación del método jugar.
     * @param tablero Tablero actual.
     * @return Coordenadas jugadas.
     */
    @Override
    public int[] jugar(int[][] tableroPrimitivo) {
        this.tablero = new Tablero(tableroPrimitivo);

        //Genera las rayas en las listas.
        generarRayas(tablero);

        imprimirTablero(
                String.format("Tablero recibido por jugador %s (%s):",
                posicionJugador, (posicionJugador == 1 ? "0" : "X")), tablero);

        return solicitarJugada();
    }

    /**
     * Elige la coordenada mas conveniente para realizar la jugada.
     * @return Coordenada de la jugada.
     */
    private int[] solicitarJugada() {


        if (rayasPropias.isEmpty() && rayasRival.isEmpty()) {
            return this.jugadaRandom();//new int[]{2,3};
        }

        Ficha fichaDefensiva = null;
        Ficha fichaAtacante = null;
        Ficha fichaAux = null;
        int criticidad = Integer.MAX_VALUE;
        int criticidadAux = 0;
        int criticidadAtacante = 0;
        int criticidadDefensiva = 0;
        LinkedList<Ficha> optimasPropias = new LinkedList<Ficha>();
        LinkedList<Ficha> optimasRival = new LinkedList<Ficha>();
        LinkedList<Ficha> iguales = new LinkedList<Ficha>();
        System.out.println("\nAnalizando fichas propias:");
        for (Raya raya : rayasPropias) {
            fichaAux = raya.getFichaOptima(Owner.PROPIA);
            if (!optimasPropias.contains(fichaAux)) {
                criticidadAux = fichaAux.getCriticidad(raya.getPropiedad(), Owner.PROPIA);
                System.out.println("Analizando ficha " + fichaAux + " - Criticidad: " + criticidadAux);
                if (criticidadAux == Integer.MIN_VALUE) {
                    System.out.println("\nNo hay chance, va a ganar si o si! Ataca a muerte!!");
                    return new int[]{fichaAux.getCoordenadaX(), fichaAux.getCoordenadaY()};
                }
                optimasPropias.add(fichaAux);
                if (criticidadAux < criticidad) {
                    criticidad = criticidadAux;
                    criticidadAtacante = criticidadAux;
                    fichaAtacante = fichaAux;
                }
            }
        }

        fichaAux = null;
        criticidad = Integer.MAX_VALUE;
        criticidadAux = 0;
        System.out.println("\nAnalizando fichas rival:");
        for (Raya raya : rayasRival) {
            fichaAux = raya.getFichaOptima(Owner.PROPIA);
            if (!optimasRival.contains(fichaAux)) {
                criticidadAux = fichaAux.getCriticidad(raya.getPropiedad(), Owner.PROPIA);
                System.out.println("Analizando ficha " + fichaAux + " - Criticidad: " + criticidadAux);
                if (criticidadAux == Integer.MIN_VALUE) {
                    System.out.println("\nNo hay chance, va a peder si o si! Defiende a muerte!!");
                    return new int[]{fichaAux.getCoordenadaX(), fichaAux.getCoordenadaY()};
                }
                optimasRival.add(fichaAux);
                if (optimasPropias.contains(fichaAux)) {
                    iguales.add(fichaAux);
                }

                if (criticidadAux < criticidad) {
                    criticidad = criticidadAux;
                    criticidadDefensiva = criticidadAux;
                    fichaDefensiva = fichaAux;
                }
            }

        }




        int[] jugada = new int[2];

        if (fichaDefensiva == null) {
            System.out.println("Ataca porque no tiene otra opcion!");
            jugada[0] = fichaAtacante.coordenadaX;
            jugada[1] = fichaAtacante.coordenadaY;
            return jugada;
        }

        if (fichaAtacante == null) {
            System.out.println("Defiende porque no tiene otra opcion!");
            jugada[0] = fichaDefensiva.coordenadaX;
            jugada[1] = fichaDefensiva.coordenadaY;
            return jugada;
        }



        if (criticidadDefensiva == criticidadAtacante) {
            if (iguales.size() == 0) {
                jugada[0] = fichaAtacante.coordenadaX;
                jugada[1] = fichaAtacante.coordenadaY;
            } else {
                System.out.println("Habia iguales!!!-------------------------------------------------------------------------------------------");
                criticidad = Integer.MAX_VALUE;
                criticidadAux = 0;
                fichaAux = null;
                for (Ficha ficha : iguales) {
                    criticidadAux = ficha.getCriticidad(Owner.PROPIA, Owner.PROPIA);
                    if (criticidadAux < criticidad) {
                        criticidad = criticidadAux;
                        fichaAux = ficha;
                    }

                }
                jugada[0] = fichaAux.coordenadaX;
                jugada[1] = fichaAux.coordenadaY;
            }
            System.out.println("Defensa=Ataque (" + criticidadDefensiva + ") Elije atacar!");
            return jugada;
        }

        if (criticidadDefensiva < criticidadAtacante) {
            jugada[0] = fichaDefensiva.coordenadaX;
            jugada[1] = fichaDefensiva.coordenadaY;
            System.out.println("Hay mas posibilidades de perder. (criticidadDefensiva: " + criticidadDefensiva + " - criticidadAtacante:" + criticidadAtacante + ") Elije defender!");
        } else {
            jugada[0] = fichaAtacante.coordenadaX;
            jugada[1] = fichaAtacante.coordenadaY;
            System.out.println("Hay mas posibilidades de ganar. (criticidadDefensiva: " + criticidadDefensiva + " - criticidadAtacante:" + criticidadAtacante + ") Elije atacar!");
        }
        return jugada;


    }

    private int[] jugadaRandom() {
        int[] jugada = new int[2];
        do {
            jugada[0] = random(1, 6);
            jugada[1] = random(1, 6);
        } while (this.tablero.getFichas()[jugada[0]][jugada[1]].getOwner() != Owner.VACIA);
        return jugada;

    }

    public static int random(int a, int b) {
        if (a >= 0) {
            return (int) (Math.random() * (b - a + 1) + a);
        } else {
            return (int) (Math.random() * (b - a + 1)) + a;
        }
    }

    private void imprimirTablero(String titulo, Tablero tablero) {
        System.out.println("");
        System.out.println(titulo);
        for (int i = 0; i < 8; i++) {
            String linea = "";
            for (int j = 0; j < 8; j++) {
                if (tablero.getFichas()[i][j].getOwner() == Owner.VACIA) {
                    linea += "  ";
                } else if (tablero.getFichas()[i][j].getOwner() ==
                        (posicionJugador == 1 ? Owner.PROPIA : Owner.RIVAL)) {
                    linea += " 0";
                } else {
                    linea += " X";
                }
            }
            System.out.println(linea);
        }
    }

    private Owner determinarOwner(int valorCasilla) {
        Owner ownerCasilla;
        if (valorCasilla == 0) {
            ownerCasilla = Owner.VACIA;
        } else if (valorCasilla == posicionJugador) {
            ownerCasilla = Owner.PROPIA;
        } else {
            ownerCasilla = Owner.RIVAL;
        }
        return ownerCasilla;
    }

    /**
     * Busca en el tablero todas las rayas posibles y las carga en la lista
     * correspondiente (propias, rival, vacias).
     */
    private void generarRayas(Tablero tablero) {
        rayasPropias = new TreeSet<Raya>();
        rayasRival = new TreeSet<Raya>();
        rayasVacias = new TreeSet<Raya>();

        //Recorrer horizontales
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 5; j++) {
                verificarYCargarRaya(
                        tablero.getFichas()[i][j],
                        tablero.getFichas()[i][j + 1],
                        tablero.getFichas()[i][j + 2],
                        tablero.getFichas()[i][j + 3]);
            }
        }

        //Recorrer verticales
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 8; j++) {
                verificarYCargarRaya(
                        tablero.getFichas()[i][j],
                        tablero.getFichas()[i + 1][j],
                        tablero.getFichas()[i + 2][j],
                        tablero.getFichas()[i + 3][j]);
            }
        }

        //Recorrer diagonal 135 grados
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                verificarYCargarRaya(
                        tablero.getFichas()[i][j],
                        tablero.getFichas()[i + 1][j + 1],
                        tablero.getFichas()[i + 2][j + 2],
                        tablero.getFichas()[i + 3][j + 3]);
            }
        }

        //Recorrer diagonal 45 grados
        for (int i = 0; i < 5; i++) {
            for (int j = 7; j > 2; j--) {
                verificarYCargarRaya(
                        tablero.getFichas()[i][j],
                        tablero.getFichas()[i + 1][j - 1],
                        tablero.getFichas()[i + 2][j - 2],
                        tablero.getFichas()[i + 3][j - 3]);
            }
        }
    }

    /**
     * Verifica la raya y la carga en la lista correspondiente.
     */
    private void verificarYCargarRaya(Ficha ficha1,
            Ficha ficha2, Ficha ficha3, Ficha ficha4) {

        if (ficha1.getOwner() == Owner.VACIA &&
                ficha2.getOwner() == Owner.VACIA &&
                ficha3.getOwner() == Owner.VACIA &&
                ficha4.getOwner() == Owner.VACIA) {

            //La raya está vacía
            Raya raya = new Raya();
            raya.setCriticidad(Raya.CRITICIDAD_NULA);
            raya.setFichas(new Ficha[4]);
            raya.getFichas()[0] = ficha1;
            raya.getFichas()[1] = ficha2;
            raya.getFichas()[2] = ficha3;
            raya.getFichas()[3] = ficha4;

            //Agrega la raya a cada una de las fichas
            ficha1.getRayas().add(raya);
            ficha2.getRayas().add(raya);
            ficha3.getRayas().add(raya);
            ficha4.getRayas().add(raya);

            rayasVacias.add(raya);
            return;
        }

        Ficha[] valores = new Ficha[4];
        valores[0] = ficha1;
        valores[1] = ficha2;
        valores[2] = ficha3;
        valores[3] = ficha4;
        /**
         * recorro toda la raya buscando owner y descartar mixtas
         */
        Owner flag = Owner.VACIA;
        for (int i = 0; i < 4; i++) {
            if (valores[i].getOwner() != Owner.VACIA) {
                if (flag == Owner.VACIA) {
                    flag = valores[i].getOwner();
                } else {
                    if (flag != valores[i].getOwner()) {
                        return;
                    }
                }
            }
        }

        Owner casilla1Owner = ficha1.getOwner();
        Owner casilla2Owner = ficha2.getOwner();
        Owner casilla3Owner = ficha3.getOwner();
        Owner casilla4Owner = ficha4.getOwner();

        //La raya es del owner encontrado.
        Raya raya = new Raya();

        //Determinar criticidad.
        int cantidad = 0;
        if (casilla1Owner != Owner.VACIA) {
            cantidad++;
        }
        if (casilla2Owner != Owner.VACIA) {
            cantidad++;
        }
        if (casilla3Owner != Owner.VACIA) {
            cantidad++;
        }
        if (casilla4Owner != Owner.VACIA) {
            cantidad++;
        }
        switch (cantidad) {
            case 0:
            case 1:
                raya.setCriticidad(Raya.CRITICIDAD_BAJA);
                break;
            case 2:
                if (casilla1Owner == Owner.VACIA && casilla4Owner == Owner.VACIA) {
                    raya.setCriticidad(Raya.CRITICIDAD_ALTA);
                } else {
                    raya.setCriticidad(Raya.CRITICIDAD_NORMAL);
                }
                break;
            case 3:
                raya.setCriticidad(Raya.CRITICIDAD_URGENTE);
                break;
            default:
        }

        //Determinar las fichas de la raya.
        raya.setFichas(new Ficha[4]);

        raya.getFichas()[0] = ficha1;
        raya.getFichas()[1] = ficha2;
        raya.getFichas()[2] = ficha3;
        raya.getFichas()[3] = ficha4;

        //Agrega la raya a cada una de las fichas
        ficha1.getRayas().add(raya);
        ficha2.getRayas().add(raya);
        ficha3.getRayas().add(raya);
        ficha4.getRayas().add(raya);

        if (flag == Owner.PROPIA) {
            rayasPropias.add(raya);
        } else {
            rayasRival.add(raya);
        }
    }

    // <editor-fold defaultstate="collapsed" desc="Clases internas">
    /**
     * Clase Raya.
     */
    class Raya implements Comparable<Raya> {

        private Ficha[] fichas = new Ficha[4];
        private int criticidad = 0;
        /**
         * solo tiene ocupado  creo casillero
         */
        public static final int CRITICIDAD_NULA = 0;
        /**
         * solo tiene ocupado un  casillero
         */
        public static final int CRITICIDAD_BAJA = 1;
        /**
         * posee opcupado dos casilleros con fichas contra un borde o ficha rival
         */
        public static final int CRITICIDAD_NORMAL = 2;
        /**
         * posee ocupado dos casilleros contiguos con sus dos extremos libres
         */
        public static final int CRITICIDAD_ALTA = 3;
        /**
         * posee ocupado 3 casilleros
         */
        public static final int CRITICIDAD_URGENTE = 4;

        public Raya() {
        }

        public int getCriticidad() {
            return criticidad;
        }

        public void setCriticidad(int criticidad) {
            this.criticidad = criticidad;
        }

        public Ficha[] getFichas() {
            return fichas;
        }

        public void setFichas(Ficha[] fichas) {
            this.fichas = fichas;
        }

        public int getCantidadLugaresVacios() {
            int vacios = 0;
            for (Ficha ficha : fichas) {
                if (ficha.isVacio()) {
                    vacios++;
                }
            }
            return vacios;
        }

        public Ficha getFichaOptima(Owner ownerEjecutor) {
            int criticidadAux = 0;
            Ficha retorno = null;
            for (Ficha ficha : fichas) {
                if (ficha.getOwner() == Owner.VACIA) {
                    criticidadAux = ficha.getCriticidad(this.getPropiedad(), ownerEjecutor);
                    if (criticidadAux < criticidad) {
                        criticidad = criticidadAux;
                        retorno = ficha;
                    }
                }
            }
            return retorno;
        }

        public int getCriticidadResultante(Ficha ficha) {
            Ficha fichaCambiada = null;
            int retorno = 0;

            for (int i = 0; i < this.fichas.length; i++) {
                if (this.fichas[i].equals(ficha)) {
                    this.fichas[i].setOwner(this.getPropiedad());
                    fichaCambiada = this.fichas[i];
                    break;
                }
            }

            int cantidad = 0;
            if (this.fichas[0].getOwner() != Owner.VACIA) {
                cantidad++;
            }
            if (this.fichas[1].getOwner() != Owner.VACIA) {
                cantidad++;
            }
            if (this.fichas[2].getOwner() != Owner.VACIA) {
                cantidad++;
            }
            if (this.fichas[3].getOwner() != Owner.VACIA) {
                cantidad++;
            }
            switch (cantidad) {
                case 0:
                    retorno = Raya.CRITICIDAD_NULA;
                    break;
                case 1:
                    retorno = Raya.CRITICIDAD_BAJA;
                    break;
                case 2:
                    if (this.fichas[0].getOwner() == Owner.VACIA && this.fichas[3].getOwner() == Owner.VACIA) {
                        retorno = Raya.CRITICIDAD_ALTA;
                    } else {
                        retorno = Raya.CRITICIDAD_NORMAL;
                    }
                    break;
                case 3:
                    retorno = Raya.CRITICIDAD_URGENTE;
                    break;
                default:
            }
            fichaCambiada.setOwner(Owner.VACIA);

            return retorno;

        }

        public Owner getPropiedad() {

            for (int i = 0; i < fichas.length; i++) {
                Ficha ficha = fichas[i];
                if (ficha != null && ficha.getOwner() != Owner.VACIA) {
                    return ficha.getOwner();
                }
            }
            return Owner.VACIA;
        }

        /**
         * Compara las rayas por criticidad.
         * @param o Otra raya a comparar.
         * @return Diferencia de criticidades entre la otra raya y la actual.
         */
        public int compareTo(Raya o) {
            int res = o.criticidad - criticidad;
            return (res == 0 ? -1 : res);
        }

        @Override
        public String toString() {
            return "[ " + this.fichas[0] + "-" + this.fichas[1] + "-" + this.fichas[2] + "-" + this.fichas[3] + "  C:" + this.criticidad + " P:" + this.getPropiedad();
        }
    }

    /**
     * Clase Ficha.
     */
    class Ficha {

        private int coordenadaX;
        private int coordenadaY;
        private boolean vacio;
        private Owner owner;
        private List<Raya> rayas = new LinkedList<Raya>();

        public Ficha() {
        }

        public Ficha(int coordenadaX, int coordenadaY, boolean vacio, Owner owner) {
            this.coordenadaX = coordenadaX;
            this.coordenadaY = coordenadaY;
            this.vacio = vacio;
            this.owner = owner;
        }

        public Owner getOwner() {
            return owner;
        }

        public void setOwner(Owner owner) {
            this.owner = owner;
        }

        public int getCoordenadaX() {
            return coordenadaX;
        }

        public void setCoordenadaX(int coordenadaX) {
            this.coordenadaX = coordenadaX;
        }

        public int getCoordenadaY() {
            return coordenadaY;
        }

        public void setCoordenadaY(int coordenadaY) {
            this.coordenadaY = coordenadaY;
        }

        public boolean isVacio() {
            return vacio;
        }

        public void setVacio(boolean vacio) {
            this.vacio = vacio;
        }

        public List<Raya> getRayas() {
            return rayas;
        }

        public void setRayas(List<Raya> rayas) {
            this.rayas = rayas;
        }

        public int getCriticidad(Owner ownerRaya, Owner ownerEjecutor) {
            if (this.owner != Owner.VACIA) {
                return Integer.MAX_VALUE;
            }
            int criticidadPropias = 0;
            int criticidadRival = 0;
            int criticidadResultante = 0;
            int minimoJugadasGanar = 4;
            for (Raya raya : getRayas()) {
                if (raya.getPropiedad() == ownerRaya) {
                    if (raya.getCantidadLugaresVacios() < minimoJugadasGanar) {
                        minimoJugadasGanar = raya.getCantidadLugaresVacios();
                    }


                    if (raya.getCriticidad() == Raya.CRITICIDAD_BAJA) {
                        if (raya.getCriticidadResultante(this) == Raya.CRITICIDAD_ALTA) {
                            criticidadPropias += (Raya.CRITICIDAD_ALTA);
                        } else {
                            criticidadPropias += (Raya.CRITICIDAD_NORMAL);
                        }
                    }
                    if (raya.getCriticidad() == Raya.CRITICIDAD_NORMAL) {
                        criticidadPropias += (Raya.CRITICIDAD_URGENTE);
                    }
                    if (raya.getCriticidad() == Raya.CRITICIDAD_ALTA) {
                        criticidadPropias += (Raya.CRITICIDAD_URGENTE * 1000);
                    }
                    if (raya.getCriticidad() == Raya.CRITICIDAD_URGENTE) {
                        return Integer.MIN_VALUE;
                    }
                    criticidadResultante = raya.getCriticidadResultante(this);
                    if (criticidadResultante == Raya.CRITICIDAD_URGENTE) {
                        criticidadPropias += 1000;//(Raya.CRITICIDAD_URGENTE*10000);
                    }
                }
                if (raya.getPropiedad() != owner && raya.getPropiedad() != Owner.VACIA) {
                    criticidadResultante = raya.getCriticidadResultante(this);
                    if (criticidadResultante == Raya.CRITICIDAD_URGENTE) {
                        criticidadRival += (Raya.CRITICIDAD_URGENTE * 100);
                    }
                    if (criticidadResultante == Raya.CRITICIDAD_ALTA) {
                        criticidadRival += (Raya.CRITICIDAD_URGENTE * 100);
                    }
                    if (criticidadResultante == (Raya.CRITICIDAD_NORMAL)) {
                        criticidadRival += (Raya.CRITICIDAD_URGENTE * 10);
                    }
                }

            }
            if (ownerEjecutor == ownerRaya) {
                criticidadPropias = criticidadPropias * 10;
            }
            // else
            // criticidadRival=criticidadRival*100;
            return minimoJugadasGanar - criticidadPropias - criticidadRival;

        }

        /**
         * Calcula la cantidad de fichas del propietario especificado en el contorno
         * de la ficha actual.
         * @param tablero Tablero con todas las fichas.
         * @param owner Propietario de la ficha (Vacio, propio o rival).
         * @return Cantidad de fichas.
         */
        public int getCantidadFichasEnContorno(Tablero tablero, Owner owner) {
            int cantidad = 0;

            int desdeX = (coordenadaX > 0 ? coordenadaX - 1 : 0);
            int hastaX = (coordenadaX < 8 ? coordenadaX + 1 : 8);
            int desdeY = (coordenadaY > 0 ? coordenadaY - 1 : 0);
            int hastaY = (coordenadaY < 8 ? coordenadaY + 1 : 8);

            for (int i = desdeX; i < hastaX; i++) {
                for (int j = desdeY; j < hastaY; j++) {
                    if (tablero.getFichas()[i][j].getOwner() == owner) {
                        cantidad++;
                    }
                }
            }

            return cantidad;
        }

        @Override
        public String toString() {
            return "(" + this.coordenadaX + "," + this.coordenadaY + ") O:" + this.owner;
        }

        @Override
        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
        public boolean equals(Object obj) {
            Ficha ficha = (Ficha) obj;
            if (this.coordenadaX == ficha.getCoordenadaX() && this.coordenadaY == ficha.getCoordenadaY()) {
                return true;
            }
            return false;
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 19 * hash + this.coordenadaX;
            hash = 19 * hash + this.coordenadaY;
            return hash;
        }
    }

    /**
     * Enumeración que indica el dueño de la ficha/raya.
     */
    enum Owner {

        PROPIA,
        RIVAL,
        VACIA
    }

    class Tablero implements Cloneable {

        private Ficha[][] fichas;

        public Tablero(int[][] tablero) {
            fichas = new Ficha[8][8];
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    Owner casillaOwner = determinarOwner(tablero[i][j]);
                    fichas[i][j] = new Ficha(i, j, casillaOwner == Owner.VACIA, casillaOwner);
                }
            }
        }

        public Tablero(Ficha[][] tablero) {
            fichas = new Ficha[8][8];
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    Owner casillaOwner = tablero[i][j].getOwner();
                    fichas[i][j] = new Ficha(i, j, casillaOwner == Owner.VACIA, casillaOwner);
                }
            }
        }

        public Ficha[][] getFichas() {
            return fichas;
        }

        public void setFichas(Ficha[][] fichas) {
            this.fichas = fichas;
        }

        /**
         * Clona el tablero y sus fichas.
         * @return Nuevo tablero con fichas clonadas.
         */
        @Override
        protected Object clone() {
            return new Tablero(fichas);
        }
    }
    // </editor-fold>
}
